package com.yzd.learnjava.atunit;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import com.yzd.learnjava.annotaions.Test;
import com.yzd.learnjava.io.ProcessFiles;
import com.yzd.learnjava.util.BinaryFile;
import com.yzd.learnjava.util.Print;

public class AtUnit implements ProcessFiles.Strategy{
	static Class<?> testClass ;
	static  List<String> failedTests = new ArrayList<String>();
	static long testsRun = 0;
	static long failures = 0;
	
	public static void main(String[] args) throws Exception {
		ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
		new ProcessFiles(new AtUnit(), "class").start(new String[] {"annotaions\\AtUnitExample1"});
		if(failures ==0) {
			Print.print("OK (" + testsRun + " tests)");
		} else {
			Print.print("(" + testsRun + " tests)");
			Print.print("\n>>> " + failures + " FAILURE" + (failures > 1 ? "S" : ""));
			for(String failed : failedTests) {
				Print.print("  " + failed);
			}
		}
	}
	
	public void process(File file) {
		//file = new File("D:\\src\\git\\java2\\javalearn\\bin\\com\\yzd\\learnjava\\annotaions\\AtUnitExample1.class");
		//file = new File("D:\\src\\git\\java2\\javalearn\\bin\\com\\yzd\\learnjava\\annotaions\\AtUnitExample2.class");
		file = new File("D:\\src\\git\\java2\\javalearn\\bin\\com\\yzd\\learnjava\\annotaions\\AtUnitExample3.class");
		try {
			String cName = ClassNameFinder.thisClass(BinaryFile.read(file));
			Print.print("className : " + cName);
			if(!cName.contains(".")) {
				return;
			}
			testClass = Class.forName(cName);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		TestMethods testMethods = new TestMethods();
		Method creator = null;
		Method cleanup = null;
		for(Method m : testClass.getDeclaredMethods()) {
			testMethods.addIfTestMethod(m);
			if(creator == null) {
				creator = checkForCreatorMethod(m);
			}
			
			if(cleanup == null) {
				cleanup = checkForCleanupMethod(m);
			}
		}
		
		if(testMethods.size() > 0) {
			if(creator == null) {
				try {
					if(!Modifier.isPublic(testClass.getDeclaredConstructor().getModifiers())) {
						Print.print("Error : " + testClass + " default constructor must be public");
						System.exit(1);
					}
				} catch (Exception e) {}
			}
			Print.print(testClass.getName());
		}
		for(Method m : testMethods) {
			Print.printnb("  ." + m.getName() + " ");
			try {
				Object testObject = createTestObeject(creator);
				boolean success = false ;
				try {
					if(m.getReturnType().equals(boolean.class)) {
						success = (Boolean)m.invoke(testObject);
					} else {
						m.invoke(testObject);
						success = true;
					}
				} catch (Exception e) {
					Print.print(e.getCause());
				}
				Print.print(success ? "" : "(failed)");
				testsRun++;
				if(!success) {
					failures++;
					failedTests.add(testClass.getName() + ": " + m.getName());
				}
				if(cleanup != null) {
					cleanup.invoke(testObject, testObject);
				}
			} catch (Exception e) {
				Print.print(e.getCause());
			}
		}
	}
	
	static class TestMethods extends ArrayList<Method>{
		void addIfTestMethod(Method m) {
			if(m.getAnnotation(Test.class) == null) {
				return;
			}
			
			if(!(m.getReturnType().equals(boolean.class) || m.getReturnType().equals(void.class))){
				throw new RuntimeException("@Test method must return boolen or void ");
			}
			
			m.setAccessible(true);
			add(m);
		}
	}
	
	private static Method checkForCreatorMethod(Method m) {
		if(m.getAnnotation(TestObjectCreate.class) == null) {
			return null;
		}
		
		if(!m.getReturnType().equals(testClass)) {
			throw new RuntimeException("@TestObjectCreate must return instance of Class to be tested");
		}
		
		if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) < 1) {
			throw new RuntimeException("@TestObjectCreate must be static");
		}
		m.setAccessible(true);
		return m;
	}
	
	private static Method checkForCleanupMethod(Method m) {
		if(m.getAnnotation(TestObjectCleanup.class) == null) {
			return null;
		}
		
		if(!m.getReturnType().equals(void.class)) {
			throw new RuntimeException("@TestObjectCleanup must return void");
		}
		
		if((m.getModifiers() & Modifier.STATIC) < 1) {
			throw new RuntimeException("@TestObjectCleanup must be static");
		}
		
		if(m.getParameterTypes().length == 0 || m.getParameterTypes()[0] != testClass) {
			throw new RuntimeException("@TestObjectCleanup must take an argument of the tested type");
		}
		m.setAccessible(true);
		return m;
	}
	
	private static Object createTestObeject(Method creator) {
		if(creator != null) {
			try {
				return creator.invoke(testClass);
			} catch (Exception e) {
				throw new RuntimeException("Could not run @TestObject (creator) method");
			}
		} else {
			try {
				return testClass.newInstance();
			} catch (Exception e) {
				throw new RuntimeException("Could not create a test object, try using a @TestObject method");
			}
		}
	}
}



















































